
#include	<system/kernel.h>
#include	<system/libk.h>
#include	"drivers/vga.h"

struct	console_t
{
	int_t	x, y;
	uchar_t	color;
	uchar_t	buffer[80*25*2];	// TODO: buffer por alocação dinamica (kmalloc)
}__attribute__((packed));



static	struct console_t	consoles[CONSOLE_COUNT];
static	uint_t		current_console = 0;
static	char_t*		addr;
static	uint_t		size;


int_t		to_offset(int_t x, int_t y)
{
	return	(int_t)((y * vga_width()) + x);
}

void	console_clear(uint_t n)
{
	int_t	i;
	if(n < CONSOLE_COUNT)
	{
		if(n == current_console)
			for(i = 0; i < (vga_width() * vga_height() * 2); i+=2)
				*((ushort_t*)(addr+i)) = ' ' | (consoles[current_console].color << 8);
		else
			for(i = 0; i < (vga_width() * vga_height() * 2); i+=2)
			{
				consoles[n].buffer[i] = ' ';
				consoles[n].buffer[i+1] = consoles[n].color;
			}
	}
}

void	cls()
{
	console_clear(current_console);
}

void	console_switch(uint_t n)
{
	if(n < CONSOLE_COUNT)
	{
		memcpy(&consoles[current_console].buffer, addr, size);	// salva console atual
		memcpy(addr, &consoles[n].buffer, size);	// restaura novo console
		vga_set_cursor(to_offset(consoles[n].x, consoles[n].y));	// atualiza cursor
		current_console = n;
	}
}

void	console_install()
{
	// alloca memória para os consoles
	// se o console estiver atualmente em uso tudo q for escrito nele irá diretamente para Memória de vídeo
	// ao trocar de console copiar tudo da Memoria de video para a memoria do console anterior
	// copiar memoria do proximo console na memória de video e fazer o proximo console apontar para memória de vídeo! :^)
	vga_detect();

	size = vga_width() * vga_height() * 2;
	uint_t	i;
	for(i = 0; i < CONSOLE_COUNT; i++)
	{
		//consoles[i].mem = (uchar_t*)kzmalloc(size);
		consoles[i].x = 0;
		consoles[i].y = 0;
		consoles[i].color = 0x7+i;
		console_clear(i);
	}
	addr = (char_t*)physical2virtual((void*)vga_address());
	console_switch(0);
	cls();
}


uint_t	console_current()
{
	return current_console;
}

void	roll_line()
{
	memcpy(addr, addr+(vga_width() * 2), size - (vga_width() * 2));
	bzero((addr+(size-(vga_width() * 2))), vga_width() * 2);
	consoles[current_console].y--;	
}

static void	itoa (char *buf, int base, int d)
{
	char *p = buf;
	char *p1, *p2;
	unsigned long ud = d;
	int divisor = 10;
     
	/* If %d is specified and D is minus, put `-' in the head. */
	if (base == 'd' && d < 0)
	{
		*p++ = '-';
		buf++;
		ud = -d;
	}
	else if (base == 'x')
	divisor = 16;
     
	/* Divide UD by DIVISOR until UD == 0. */
	do
	{
		int remainder = ud % divisor;
     
		*p++ = (remainder < 10) ? remainder + '0' : remainder + 'a' - 10;
	}
	while (ud /= divisor);
     
	/* Terminate BUF. */
	*p = 0;
     
	/* Reverse BUF. */
	p1 = buf;
		p2 = p - 1;
	while (p1 < p2)
	{
		char tmp = *p1;
		*p1 = *p2;
		*p2 = tmp;
		p1++;
		p2--;
	}
}

    /* Put the character C on the screen. */
void	putchar (int c)
{
	if (c == '\n' || c == '\r')
	{
	newline:
		consoles[current_console].x = 0;
		consoles[current_console].y++;
		if (consoles[current_console].y >= vga_height())
			roll_line();
		vga_set_cursor(to_offset(consoles[current_console].x, consoles[current_console].y));	// atualiza cursor
		return;
	}

	if(c == '\t')
	{
		putchar(' ');
		putchar(' ');
		putchar(' ');
		return;
	}
     
	*((uchar_t*)(addr + (consoles[current_console].x + consoles[current_console].y * vga_width()) * 2)) = (uchar_t)(c & 0xFF);
	*((uchar_t*)(addr + ((consoles[current_console].x + consoles[current_console].y * vga_width()) * 2) + 1)) = consoles[current_console].color;
     
	consoles[current_console].x++;
	if (consoles[current_console].x >= vga_width())
		goto newline;
	vga_set_cursor(to_offset(consoles[current_console].x, consoles[current_console].y));	// atualiza cursor
}

     
     /* Format a string and print it on the screen, just like the libc
        function printf. */
void	kprintf (const char *format, ...)
{
	char **arg = (char **) &format;
	int c;
	char buf[20];
     
	arg++;
     
	while ((c = *format++) != 0)
	{
		if (c != '%')
		putchar (c);
		else
		{
			char *p;
     
			c = *format++;
			switch (c)
			{
				case 'd':
				case 'u':
				case 'x':
					itoa (buf, c, *((int *) arg++));
					p = buf;
					goto string;
					break;
     
				case 's':
					p = *arg++;
					if (! p)
					p = "(null)";
     
			string:
				while (*p)
					putchar (*p++);
					break;
     
				default:
					putchar (*((int *) arg++));
					break;
			}
		}
	}
}

